home *** CD-ROM | disk | FTP | other *** search
/ NeXT Education Software Sampler 1992 Fall / NeXT Education Software Sampler 1992 Fall.iso / Programming / Classes / serialComm / SerialCommDevice.m < prev    next >
Encoding:
Text File  |  1992-08-03  |  25.7 KB  |  952 lines

  1.  
  2. /*    SerialCommDevice.m
  3.  
  4.     Synopsis:
  5.     This class defines an easy to use, robust, fault-tollerant, full-duplex serial 
  6.     communications protocol.  The implementation uses a packet-based protocol that 
  7.     transmits only printable ascii characters. 
  8.     
  9.     Description:
  10.     The actual transmission protocol only allows for the transfer of printable ascii 
  11.     characters.  When necessary, any non-printable characters are transparently en/decoded 
  12.     to effectively support the transfer of all 8-bits worth of information contained w/in
  13.     each byte.    This encoding scheme isn't particularly efficient in that any non-printable 
  14.     ascii characters end up requiring two characters for their internal representation.  
  15.     For instance, a packet containing all '\0' characters,  would end up taking twice as 
  16.     long to transfer as a packet containing the same number of 'a' characters 8^(.  Future 
  17.     enhancements should use a more efficient encoding scheme or a mechanism for compressing 
  18.     the data before transmission.  Additionally, the protocol and encoding schemes reserve the
  19.     following five printable characters:  '<', '>', '$', '%', and '&'.  Therefore, these 
  20.     characters end up requiring the same two character encoding scheme as the non-printable 
  21.     ascii characters.
  22.  
  23.     Each packet has the following format:
  24.     
  25.      Offset             Size        Description
  26.     ------            ----        ------------------------------------------------------------------
  27.     0                1            SOP_CHAR_CODE '<' Defines start of packet.
  28.     1                1            Packet Type Field.  
  29.     2                1            Packet Sequence character - to distinguish packets @ receiver.
  30.                     <0 or more data bytes here>
  31.     n-CSUM_CHARS    CSUM_CHARS    hex-encoded checksum of bytes 1 thru n-1 inclusive.
  32.     n                1            EOP_CHAR_CODE '>' Defines end of packet.
  33.  
  34.     where the following Packet Types are currently defined:
  35.         D - DATA Packet - 
  36.         A - ACK Packet - Acknowledges reciept of specified pkt. 
  37.         N - NACK Packet - A bad packet was received - request retransmit.
  38.         T - TEST Packet - Receiver just loops data back - sort of a loop-back test.
  39.         O - OOL Packet - Out-Of-Line data packet - gets sent to remote's delegate via -oolMessage:len:
  40.         
  41.     Additionally, three other chars (MAP0_CHAR_CODE thru MAP2_CHAR_CODE are '$', '%', '&' respectively) 
  42.     have special meaning and are used in an encoding scheme that allows all 256 possible character 
  43.     codes to map into the printable set of ASCII characters.
  44.  
  45.  */
  46. #import "SerialCommDevice.h"
  47. #import <sys/types.h>
  48. #import <sys/ttydev.h>
  49. #import <sys/ioctl.h>
  50. #import <fcntl.h>
  51. #import <stdio.h>
  52. #import <limits.h>
  53. #import <libc.h>
  54. #import <stdarg.h>
  55. #import <ctype.h>
  56. #import <mach/cthreads.h>
  57. #import <ansi/errno.h>
  58. /*    reserved characters: */
  59. #define         SOP_CHAR_CODE        0x3c    /*    '<' start of packet character. */
  60. #define         EOP_CHAR_CODE        0x3e    /*    '>' end of packet character. */
  61. #define         MAP0_CHAR_CODE        (0x24)    /* '$' maps 0-0x1f,SOP, EOP and MAP0-2 CHAR_CODES into 20 thru 5f. */ 
  62. #define         MAP1_CHAR_CODE        (0x25)    /* '%' maps 0x80-0xbf into 20 thru 5f. */ 
  63. #define         MAP2_CHAR_CODE        (0x26)    /* '&' maps 0xc0-0xff into 20 thru 5f. */ 
  64. #define         MAP_CHAR(c)            (((u_char)(c)>=MAP0_CHAR_CODE)&&((u_char)(c)<=MAP2_CHAR_CODE))
  65. #define            PKT_CHAR(c)            (((c)==SOP_CHAR_CODE)||((c)==EOP_CHAR_CODE))
  66. #define            MAP0_RANGE(c)        ((((u_char)(c)>=0)&&(((u_char)c)<=0x1f))||PKT_CHAR(c)||MAP_CHAR(c)||((c)==0x7f))
  67. #define            MAP1_RANGE(c)        (((u_char)(c)>=0x80)&&(((u_char)c)<=0xbf))
  68. #define            MAP2_RANGE(c)        (((u_char)(c)>=0xc0)&&(((u_char)c)<=0xff))
  69. #define            NORMAL_CHAR(c)        (((u_char)(c)>=0x20)&&(((u_char)c)<=0x7e)&&!MAP_CHAR(c)&&!PKT_CHAR(c))
  70. #define            ILLEGAL_CHAR(c)        (((u_char)(c)<0x20)||(((u_char)c)>0x7e))
  71. #define         MAP_MASK             0x3f
  72. #define         MAP_OFFSET            0x3f
  73.  
  74.  
  75. /*    checksum may be computed with from 1 to 8 bytes - change all four of the following defines at the same time: */
  76. #define         CSUM_CHARS            4            /* must fit into int (8 max) */
  77. #define         CSUM_MSB            0x8000    
  78. #define            CSUM_MASK            0xffff
  79. #define            CSUM_FMT            "%04x"
  80.  
  81. /* defined packet types: */    
  82. #define         DATA_PKT            'D'
  83. #define         ACK_PKT                'A'
  84. #define         NACK_PKT            'N'
  85. #define            TEST_PKT            'T'
  86. #define            OOL_PKT                'O'
  87. #define            VALID_PKT_TYPE(c) ((c == DATA_PKT) || (c == ACK_PKT) || (c == NACK_PKT) || (c == TEST_PKT) || (c == OOL_PKT))
  88.  
  89. #define         NEXT_ID_CHAR(pktid)    ((pktid++%10)+'0')
  90. #define            NACK_ID                '?'
  91. #define         RCV_IN_BASKET ((self->dataPktIndex)?1:0)
  92. #define         RCV_OUT_BASKET (!RCV_IN_BASKET)
  93.  
  94. /* defaults: */
  95. #define         XMIT_RETRY_COUNT    10
  96. #define            XMIT_TIMEOUT_MSECS    10000
  97. #define         RECV_TIMEOUT_MSECS    0
  98.  
  99. static int         debugFlag =         1;
  100. static void     theListener(SerialCommDevice *self);
  101.  
  102. /*    stringForCharArray()
  103.     Returns a string with printable representation of array of any ascii chars.  For debug only.
  104.  */
  105. static char *stringForCharArray(char *str, int len)
  106. {
  107.     static char            result[128];
  108.     char                *cp=result;
  109.     int                    i;
  110.  
  111.     for (i=0;i<len;i++) {
  112.         if (isprint(str[i]))
  113.             sprintf(cp,"%c",str[i]);
  114.         else
  115.             sprintf(cp,"<0x%02x>",(unsigned)str[i]);
  116.         cp = index(cp,'\0');
  117.     }
  118.     return result;
  119. }
  120.  
  121. /*    microTimeStampA() 
  122.     Accumulative time stamp. Returns a positive integer value of microseconds 
  123.     since the since the timer was initialized or a negative number if greater 
  124.     than 2048 seconds have elapsed. oldTime is used to keep track of the start
  125.     time.  It is therefore important that it be consistant between calls. To 
  126.     initialize the timer, call with oldTime.tv_sec == oldTime.tv_usec == 0.
  127.  */
  128. #define MAX_MTSA 2048000000
  129. int microTimeStampA(struct timeval * oldTime)
  130. {
  131.     struct timeval  nt;
  132.     long            elapsedTime = (-1);
  133.  
  134.     if (gettimeofday(&nt, (struct timezone *) 0) < 0)
  135.         perror("gettimeofday() failed.");
  136.  
  137.     if (oldTime->tv_sec == 0 && oldTime->tv_usec == 0) {
  138.         elapsedTime = 0;  /* just initing oldTime struct. */
  139.         *oldTime = nt;
  140.     }
  141.     else {
  142.         elapsedTime = (nt.tv_sec - oldTime->tv_sec) * 1000000;
  143.         if (nt.tv_usec < oldTime->tv_usec)
  144.             elapsedTime += nt.tv_usec + 1000000 - oldTime->tv_usec;
  145.         else
  146.             elapsedTime += nt.tv_usec - oldTime->tv_usec;
  147.     }
  148.     if (elapsedTime > MAX_MTSA)
  149.         elapsedTime = (-1);
  150.  
  151.     return ((int)elapsedTime);
  152. }
  153.  
  154. static int baudRateCode(baudRateType br)
  155. {
  156.     int            brc;
  157.     switch (br) {
  158.         case br38400:
  159.             brc=EXTB;break;
  160.         case br19200:
  161.             brc=EXTA;break;
  162.         case br9600:
  163.             brc=B9600;break;
  164.         case br4800:
  165.             brc=4800;break;
  166.         case br2400:
  167.             brc=B2400;break;
  168.         case br1200:
  169.             brc=B1200;break;
  170.         case br600:
  171.             brc=B600;break;
  172.         case br300:
  173.             brc=B300;break;
  174.         case br200:
  175.             brc=B9600;break;
  176.         case br150:
  177.             brc=B150;break;
  178.         case br134:
  179.             brc=B134;break;
  180.         case br110:
  181.             brc=B110;break;
  182.         case br75:
  183.             brc=B75;break;
  184.         case br50:
  185.             brc=B50;break;
  186.         default:
  187.             brc=B9600;break;
  188.     }
  189.     return brc;
  190. }
  191.  
  192. char *portString(sccPortType thePort)
  193. {
  194.     char         *p;
  195.     
  196.     if (thePort == sccPortB)
  197.         p = "/dev/ttyb";
  198.     else
  199.         p = "/dev/ttya";
  200.     return p;
  201. }
  202.  
  203.  
  204. /*    sumCheck()
  205.     add each character to right-rotated previous parital result.
  206.  */
  207. static int sumCheck(u_char *str, int len)
  208. {
  209.     register int         i,
  210.                         checkSum=13;
  211.  
  212.     for (i=0;i<len;i++) {
  213.         if (str[i] & 0x1)
  214.             checkSum += (((str[i] & 0xff) >> 1) | CSUM_MSB) ;
  215.         else
  216.             checkSum += ((str[i] & 0xff) >> 1);
  217.     }
  218.  
  219.     return (checkSum & CSUM_MASK);
  220. }
  221.  
  222.  
  223. /*     stringEncode()
  224.     Process the binary input string and map all into printable 
  225.     ascii in the output string. Returns the length of the output string.
  226.  */    
  227. static int stringEncode(char *input, int len, char *output)
  228. {
  229.     register int            i;
  230.     register char            *rPtr, *wPtr;
  231.     
  232.     rPtr = input;
  233.     wPtr = output;
  234.     for (i=0;(i<len)&&((wPtr-input)<(SCD_MAX_PACKETSIZE-(CSUM_CHARS+4)));i++) {
  235.         if (NORMAL_CHAR(*rPtr)) {
  236.             *wPtr++ = *rPtr++;
  237.         } else if (MAP0_RANGE(*rPtr)) {
  238.             *wPtr++ = MAP0_CHAR_CODE;
  239.             *wPtr++ = (*rPtr++ & MAP_MASK) + MAP_OFFSET;
  240.         } else if (MAP1_RANGE(*rPtr)) {
  241.             *wPtr++ = MAP1_CHAR_CODE;
  242.             *wPtr++ = (*rPtr++ & MAP_MASK) + MAP_OFFSET;
  243.         } else if (MAP2_RANGE(*rPtr)) {
  244.             *wPtr++ = MAP2_CHAR_CODE;
  245.             *wPtr++ = (*rPtr++ & MAP_MASK) + MAP_OFFSET;
  246.         }
  247.     }
  248.     return (wPtr-output);
  249. }
  250.  
  251.  
  252. /*     stringDecode()
  253.     Process the printable ascii string, expanding character MAP sequences
  254.     in the output string. Returns the length of the output string.
  255.  */    
  256. static int stringDecode(char *input, int len, char *output)
  257. {
  258.     register int            i;
  259.     register char            *rPtr, *wPtr;
  260.     
  261.     rPtr = input;
  262.     wPtr = output;
  263.     for (i=0;(i<len)&&((wPtr-input)<(SCD_MAX_PACKETSIZE-8));i++,wPtr++,rPtr++) {
  264.         if (NORMAL_CHAR(*rPtr)) {
  265.             *wPtr = *rPtr;
  266.         } else if (*rPtr == MAP0_CHAR_CODE) {
  267.             rPtr++;i++;
  268.             *wPtr = *rPtr - MAP_OFFSET;
  269.             if (*wPtr == 0x3f)
  270.                 *wPtr = 0x7f;
  271.         } else if (*rPtr == MAP1_CHAR_CODE) {
  272.             rPtr++;i++;
  273.             *wPtr = (0x80 + *rPtr - MAP_OFFSET);
  274.         } else if (*rPtr == MAP2_CHAR_CODE) {
  275.             rPtr++;i++;
  276.             *wPtr = (0xc0 + *rPtr - MAP_OFFSET);
  277.         }
  278.     }
  279.     return (wPtr-output);
  280. }
  281.  
  282. @implementation SerialCommDevice
  283.  
  284. - setRecvTimeoutMsecs:(int)msecs {recvTimeoutMsecs = msecs;return self;}
  285. - (int)recvTimeoutMsecs {return recvTimeoutMsecs;}
  286. - setXmitTimeoutMsecs:(int)msecs {xmitAckTimeoutMsecs = msecs;return self;}
  287. - (int)xmitTimeoutMsecs {return xmitAckTimeoutMsecs;}
  288. - setmaxXmitRetries:(int)retries {maxXmitRetries = retries;return self;}
  289. - (int)maxXmitRetries {return maxXmitRetries;}
  290.  
  291.  
  292.  
  293.  
  294.  
  295. /*    write:len:
  296.  *
  297.  *    writes len chars to serial port.  
  298.  *    Returns zero on success or SCD_IO_ERR on failure.
  299.  */            
  300. -(int)write:(char *)buf len:(int)l
  301. {
  302.     int             retVal=0;
  303.     int                written=0;
  304.  
  305.     mutex_lock((mutex_t)writeLock);
  306.     while (written < l) {
  307.         if ((written += write(fd, buf, l-written)) < 0) {
  308.             retVal = SCD_IO_ERR;
  309.             break;
  310.         } 
  311.     }
  312.     mutex_unlock((mutex_t)writeLock);
  313.     cthread_yield();
  314.     return retVal;
  315. }
  316.  
  317.  
  318. /*    - hangOnReadPort:
  319.  *
  320.  *    Wait for data available or timeout, whichever occurs first. 
  321.  *    Returns 0 for success, SCD_IO_ERR for error, and SCD_TIMEOUT_ERR 
  322.  *    for timeout.
  323.  */
  324. - (int)hangOnReadPort:(int)timeoutMsecs
  325. {
  326.     struct timeval        to;
  327.     fd_set                portList;
  328.     int                 retVal;
  329.  
  330.     FD_ZERO(&portList);
  331.     FD_SET(fd, &portList);
  332.     if (timeoutMsecs > 0) {
  333.         to.tv_sec = timeoutMsecs / 1000;
  334.         to.tv_usec = (timeoutMsecs % 1000) * 1000;
  335.         retVal = select(fd+1, &portList, 0, 0, &to);
  336.     } else 
  337.         retVal = select(fd+1, &portList, 0, 0, 0);
  338.     if (retVal < 0) {
  339.         retVal = SCD_IO_ERR;
  340.         fprintf(stderr,"\nselect() failed:%s\n", strerror(errno));
  341.     }
  342.     else if (retVal > 0)
  343.         retVal = 0;
  344.     else {
  345.         retVal = SCD_TIMEOUT_ERR;
  346.         if (debugFlag)
  347.             fprintf(stderr,"[RTO]");
  348.     }
  349.     return retVal;
  350. }
  351.  
  352. /*    buildPacket:withID:data:len:buffer:
  353.  *
  354.  *    Construct the requested packet in the specified buf.  
  355.  *    Returns size of the completed packet. 
  356.  */
  357. -(int)buildPacket:(char)pType withID:(char)pktId data:(char *)data len:(int)len buffer:(char *)buf
  358. {
  359.     int            i, retVal;
  360.     
  361.     buf[0] = SOP_CHAR_CODE;
  362.     buf[1] = pType;
  363.     buf[2] = pktId;
  364.     retVal = stringEncode(data, len, &buf[3]) + 2;
  365.     i = sumCheck((u_char *)&buf[1], retVal++);
  366.     sprintf(&buf[retVal], CSUM_FMT, (unsigned)i);
  367.     buf[retVal+CSUM_CHARS] = EOP_CHAR_CODE;
  368.     return (retVal+1+CSUM_CHARS);
  369. }
  370.  
  371.  
  372.  
  373.  
  374. /*    sendPkt:data:len:pktId:
  375.  *
  376.  *    Build and send specified pkt.  If not data or test pkt, then send pkt and
  377.  *    return zero if successfull. If data or test pkt, wait for reply and return
  378.  *     positive microseconds required for transfer if successfull.  If retries
  379.  *    become exhausted, then return SCD_XFER_ERR, if failure return SCD_IO_ERR.
  380.  */
  381. -(int)sendPkt:(char)pType data:(char *)data len:(int)len pktId:(char)asciiPktId
  382. {
  383.     int                retries=maxXmitRetries,elapsedTime=0, pktLen, retVal=0;
  384.     char            buf[SCD_MAX_PACKETSIZE];
  385.     struct timeval  t;
  386.  
  387.     if ((len+CSUM_CHARS+4) > SCD_MAX_PACKETSIZE) {
  388.         fprintf(stderr, "\nsendPkt: data to big.  Maximum allowable len is:%d\n", SCD_MAX_PACKETSIZE-(CSUM_CHARS+4));
  389.         retVal = SCD_LENGTH_ERR;
  390.     } else if ((pktLen=[self buildPacket:pType withID:asciiPktId data:data len:len buffer:buf])>SCD_MAX_PACKETSIZE){
  391.         fprintf(stderr, "\nsendPkt: data to big.\n");    
  392.         retVal = SCD_LENGTH_ERR;
  393.     } else if ((pType == DATA_PKT) || (pType == TEST_PKT)) {         /* wait for response w/these. */
  394.         mutex_lock((mutex_t)dataPktLock);
  395.         asciiAckDPktID = -1;
  396.         if (debugFlag>1)
  397.             fprintf(stderr,"\nSending Pkt:%s\n", stringForCharArray(buf,pktLen));
  398.         t = (struct timeval){0,0};
  399.         for (retVal=0;retVal==0;) {
  400.             if (retVal = [self write:buf len:pktLen])
  401.                 break;
  402.             elapsedTime = microTimeStampA(&t); /* set start time. */
  403.             for (;!retVal;) {
  404.                 if (asciiAckDPktID == asciiPktId) {
  405.                     if (elapsedTime < 0)
  406.                         retVal = MAX_MTSA;
  407.                     else 
  408.                         retVal = elapsedTime;
  409.                     break;
  410.                 }
  411.                 else if (nackDPktFlag) {
  412.                     retVal=SCD_XFER_ERR;
  413.                     if (debugFlag)
  414. //                        fprintf(stderr,"[NACK sending:%s]",stringForCharArray(buf,pktLen));
  415.                         fprintf(stderr,"[NACK]");
  416.                     nackDPktFlag = 0;
  417.                     break;
  418.                 } else {
  419.                     if (xmitAckTimeoutMsecs && (((elapsedTime/1000)>xmitAckTimeoutMsecs) || (elapsedTime < 0))) {
  420.                         t = (struct timeval){0,0};
  421.                         if (debugFlag)
  422. //                            fprintf(stderr,"[XTO sending:%s]",stringForCharArray(buf,pktLen));
  423.                             fprintf(stderr,"[XTO]");
  424.                         retVal=SCD_XFER_ERR;
  425.                         break;
  426.                     }
  427.                 }
  428.                 cthread_yield();
  429.                 elapsedTime = microTimeStampA(&t);     /* get elapsed time. */
  430.             }
  431.             if (!retVal)
  432.                 break;
  433.             else if ((retVal==SCD_XFER_ERR)&&(retries-->0)) {
  434.                 retVal = 0;
  435.             }
  436.         }
  437.         mutex_unlock((mutex_t)dataPktLock);
  438.     } else {                        /* send and forget all other pkt types. */
  439.         if (debugFlag>10)
  440.             fprintf(stderr,"\nSending Pkt:%s\n", stringForCharArray(buf,pktLen));
  441.         retVal = [self write:buf len:pktLen];
  442.     }
  443.     return retVal;
  444. }
  445.  
  446.  
  447.  
  448.  
  449. /*    xmit:,...
  450.  *
  451.  *    Send string to remote and wait for ack. Returns negative error code if
  452.  *    failure, otherwise returns positive number of milliseconds required for
  453.  *    transfer.
  454.  */
  455. - (int)xmit:(char *)fmt, ...
  456. {
  457.     va_list         args;
  458.     int                retVal;
  459.     char            localBuf[SCD_MAX_PACKETSIZE];
  460.  
  461.     va_start(args, fmt);
  462.     vsprintf(localBuf, fmt, args);
  463.     va_end(args);
  464.     retVal = [self sendPkt:DATA_PKT data:localBuf len:strlen(localBuf) pktId:NEXT_ID_CHAR(xmitPktId)];
  465.     return retVal;
  466. }
  467.  
  468.  
  469.  
  470.  
  471. /*    xmit:len:
  472.  *
  473.  *    Send fixed-length binary character array to remote and wait for ack. Returns 
  474.  *    negative error code if failure, otherwise returns positive number of milliseconds 
  475.  *    required for transfer.
  476.  */
  477. - (int)xmit:(char *)buf len:(int)len
  478. {
  479.     int                retVal;
  480.  
  481.     retVal = [self sendPkt:DATA_PKT data:buf len:len pktId:NEXT_ID_CHAR(xmitPktId)];
  482.     return retVal;
  483. }
  484.  
  485.  
  486. /*    xmitOOL:,...
  487.  *
  488.  *    Send string to remote and wait for ack. Returns negative error code or
  489.  *    positive number of microseconds required for transfer.  If remote has
  490.  *    a delegate, the delegate is sent the data via the -oolMessage:len: msg.
  491.  *
  492.  */
  493. - (int)xmitOOL:(char *)fmt, ...
  494. {
  495.     va_list         args;
  496.     int                retVal;
  497.     char            localBuf[SCD_MAX_PACKETSIZE];
  498.  
  499.     va_start(args, fmt);
  500.     vsprintf(localBuf, fmt, args);
  501.     va_end(args);
  502.     retVal = [self sendPkt:OOL_PKT data:localBuf len:strlen(localBuf) pktId:NEXT_ID_CHAR(xmitPktId)];
  503.     return retVal;
  504. }
  505.  
  506.  
  507.  
  508. /*    xmitOOL:len:
  509.  *
  510.  *    Send fixed-length binary character array to remote and wait for ack. Returns 
  511.  *    negative error code if failure, otherwise returns positive number of milliseconds 
  512.  *    required for transfer. If remote has a delegate, the delegate is sent the data 
  513.  *    via the -oolMessage:len: msg.
  514.  */
  515. - (int)xmitOOL:(char *)buf len:(int)len
  516. {
  517.     int                retVal;
  518.  
  519.     retVal = [self sendPkt:OOL_PKT data:buf len:len pktId:NEXT_ID_CHAR(xmitPktId)];
  520.     return retVal;
  521. }
  522.  
  523.  
  524.  
  525.  
  526. /*    test:,...
  527.  *
  528.  *    Like xmit:,..., but sends a test packet instead of a data packet.  Test
  529.  *    packets are immediatly acknowledged by the remote's receiving thread (if
  530.  *    it is up and running), and the packet is not forwarded to the receiving 
  531.  *    application.   Returns negative error code or positive number of 
  532.  *    microseconds required for transfer.
  533.  */
  534. - (int)test:(char *)fmt, ...
  535. {
  536.     va_list         args;
  537.     int                retVal;
  538.     char            localBuf[SCD_MAX_PACKETSIZE];
  539.  
  540.     va_start(args, fmt);
  541.     vsprintf(localBuf, fmt, args);
  542.     va_end(args);
  543.     retVal = [self sendPkt:TEST_PKT data:localBuf len:strlen(localBuf) pktId:NEXT_ID_CHAR(xmitPktId)];
  544.     return retVal;
  545. }
  546.  
  547.  
  548.  
  549.  
  550. /*    test:len:
  551.  *
  552.  *    Like xmit:len:, but sends a test packet instead of a data packet.  Test
  553.  *    packets are immediatly acknowledged by the remote's receiving thread (if
  554.  *    it is up and running), and the packet is not forwarded to the receiving 
  555.  *    application.   Returns negative error code or positive number of 
  556.  *    microseconds required for transfer.
  557.  */
  558. - (int)test:(char *)buf len:(int)len
  559. {
  560.     int                retVal;
  561.  
  562.     retVal = [self sendPkt:TEST_PKT data:buf len:len pktId:NEXT_ID_CHAR(xmitPktId)];
  563.     return retVal;
  564. }
  565.  
  566.  
  567.  
  568.  
  569.  
  570. /*    processPkt:len:
  571.  *
  572.  *    returns zero or greater length if valid data packet, -1 otherwise.
  573.  */
  574. -(int)processPkt:(char *)pkt len:(int)len
  575. {
  576.     int                i, 
  577.                     sum, 
  578.                     retVal = (-1);
  579.     char             buf[16], 
  580.                     *bp;
  581.  
  582.     bp = pkt+(len-CSUM_CHARS-1);
  583.     for (i=0;i<CSUM_CHARS;i++)
  584.         buf[i] = *bp++;
  585.     buf[i] = '\0';
  586.     sscanf(buf,"%x", (unsigned *)&sum);
  587.     if (sum != sumCheck((u_char *)&pkt[1], len-(CSUM_CHARS+2))) {         /* bad packet. */
  588.         [self sendPkt:NACK_PKT data:NULL len:0 pktId:NACK_ID];
  589.         if (debugFlag>1)
  590.             fprintf(stderr,"\nReceived Bad Pkt:%s\n", stringForCharArray(pkt,len));
  591.     } else {
  592.         if (pkt[1] == ACK_PKT) {
  593.             asciiAckDPktID = pkt[2];
  594.         } else if (pkt[1] == NACK_PKT) {
  595.             nackDPktFlag++; 
  596.         } else if ((pkt[1] == DATA_PKT) || (pkt[1] == OOL_PKT) || (pkt[1] == TEST_PKT)) {
  597.             [self sendPkt:ACK_PKT data:NULL len:0 pktId:pkt[2]];
  598.             if (((pkt[1] == DATA_PKT) || (pkt[1] == OOL_PKT)) && (asciiRcvDPktID != pkt[2])) {    /* then this is a new data packet. */
  599.                 if (debugFlag>1)
  600.                     fprintf(stderr,"\nReceived new Pkt(old:%c, new:%c):%s\n", asciiRcvDPktID, pkt[2], stringForCharArray(pkt,len));
  601.                 asciiRcvDPktID = pkt[2];
  602.                 if (pkt[1] == OOL_PKT) {
  603.                     if (delegate) {
  604.                         retVal = stringDecode(&pkt[3], len-(CSUM_CHARS+4), pkt); /* warning! in-place de-packetization. */
  605.                         pkt[retVal]='\0';
  606.                         [delegate oolMessage:pkt len:retVal];
  607.                         retVal = (-1);
  608.                     }
  609.                 } else {
  610.                     retVal = stringDecode(&pkt[3], len-(CSUM_CHARS+4), pkt); /* warning! in-place de-packetization. */
  611.                     pkt[retVal]='\0';
  612.                 }
  613.             } else     if (debugFlag>1) {
  614.                 fprintf(stderr,"\nReceived duplicate Pkt:%s\n", stringForCharArray(pkt,len));
  615.             }
  616.         }
  617.     }
  618.  
  619.     return retVal;
  620. }
  621.  
  622.  
  623.  
  624.  
  625. /*    pollRecvBufferReady
  626.  *
  627.  *    Returns 1 if recv Buffer contains data and is ready to be read, otherwise returns 0.
  628.  */
  629. - (int) pollRecvBufferReady
  630. {
  631.     int        retVal;
  632.     if (dataPktLen < 0)
  633.         retVal = 0;
  634.     else 
  635.         retVal = 1;
  636.     return retVal;
  637. }
  638.  
  639.  
  640.  
  641.  
  642. /*    - recv:maxLen:
  643.  *
  644.  *    Returns negative error code if failure, otherwise returns positive 
  645.  *    number of characters received.
  646.  */
  647. - (int) recv:(char *)str maxLen:(int)len
  648. {
  649.     int                    elapsedTime=0;
  650.     int                    retVal;
  651.     struct timeval      t = {0,0};
  652.  
  653.     microTimeStampA(&t);                 /* Set start time. */
  654.     for (retVal = 0;!retVal;) {
  655.         if (dataPktLen < 0) {
  656.             elapsedTime = microTimeStampA(&t)/1000;
  657.             if (recvTimeoutMsecs && ((elapsedTime > recvTimeoutMsecs) || (elapsedTime < 0))) {
  658.                 if (debugFlag)
  659.                     fprintf(stderr,"[RTO]");
  660.                 retVal=SCD_TIMEOUT_ERR;
  661.             } else {
  662.                 cthread_yield();
  663.             }        
  664.         } else {
  665.             break;
  666.         }
  667.     }
  668.     if (!retVal) {
  669.         if (dataPktLen > len) {
  670.             fprintf(stderr,"\nrecv:maxLen: actual packet length:%d exceeds expected:%d\n",dataPktLen, len);
  671.             retVal = SCD_LENGTH_ERR;
  672.         } else {
  673.             bcopy(recvBuf[RCV_OUT_BASKET], str, dataPktLen);
  674.             retVal = dataPktLen;
  675.             if (dataPktLen < (len-1))
  676.                 str[dataPktLen] = '\0';
  677.             dataPktLen = (-1);
  678.         }
  679.     }
  680.     return retVal;
  681. }
  682.  
  683.  
  684.  
  685. /*    - setPort:atRate:
  686.  *
  687.  *    Change port and/or transmission parameters.
  688.  *    Return 0 on success and negative error code if fail.
  689.  */
  690. - (int)setPort:(sccPortType)thePort atRate:(baudRateType)theRate
  691. {
  692.     struct sgttyb            sg;
  693.     int                        retVal =0;
  694.  
  695.     if ((fd > 0) && (thePort != port)) {
  696.         close(fd);
  697.         fd = (-1);
  698.     }
  699.     if (fd < 0 ) {
  700.         if ((fd = open(portString(thePort), O_RDWR)) < 0) {
  701.             fprintf(stderr,"\nopen() failed on %s:%s\n", portString(thePort),
  702.                     strerror(errno));
  703.             retVal = SCD_OPEN_ERR;
  704.         } else {
  705.     /* prevent any other unauthorized port accesses */
  706.             if (ioctl(fd, TIOCEXCL, (void*)nil) < 0) {
  707.                 fprintf(stderr,
  708.                     "\nioctl(TIOCEXCL) failed on %s:%s\n", portString(thePort),
  709.                     strerror(errno));
  710.                 retVal = SCD_OPEN_ERR;
  711.             }
  712.         }
  713.     }
  714.     
  715.     if (!retVal)  {
  716.         if (ioctl(fd, TIOCGETP, &sg) < 0) {
  717.             fprintf(stderr,
  718.                 "\nioctl(TIOCGETP) failed on %s:%s\n", portString(thePort),
  719.                 strerror(errno));
  720.             retVal = SCD_OPEN_ERR;
  721.         } else {
  722.             sg.sg_ispeed = baudRateCode(theRate);
  723.             sg.sg_ospeed = baudRateCode(theRate);
  724.             sg.sg_flags = RAW;
  725.             sg.sg_flags |= ANYP;
  726.             if (ioctl(fd, TIOCSETP, &sg) < 0) {
  727.                 fprintf(stderr,
  728.                     "\nioctl(TIOCSETP) failed on %s:%s\n", portString(thePort),
  729.                     strerror(errno));
  730.                 retVal = SCD_OPEN_ERR;
  731.             }
  732.         }
  733.     }
  734.     if (!retVal) {
  735.         port = thePort;
  736.         baudRate = theRate;
  737.     } else {
  738.         if (fd > 0)
  739.             close(fd);
  740.         port = noPort;
  741.         baudRate = brNone;
  742.     }
  743.     return retVal;
  744. }
  745.                             
  746.  
  747.  
  748.  
  749. /*    - initPort:atRate:
  750.  *
  751.  *    open specified port at specified rate. Returns nil on failure.
  752.  */
  753. - initPort:(sccPortType)thePort atRate:(baudRateType)theRate
  754. {
  755.     [super init];
  756.     dataPktLen = -1;
  757.     (mutex_t)writeLock=mutex_alloc();
  758.     mutex_init((mutex_t)writeLock);
  759.     (mutex_t)dataPktLock=mutex_alloc();
  760.     mutex_init((mutex_t)dataPktLock);
  761.     fd = (-1);
  762.     maxXmitRetries = XMIT_RETRY_COUNT;
  763.     recvTimeoutMsecs = RECV_TIMEOUT_MSECS;
  764.     if (theRate == brNone) {
  765.         baudRateType        rate;
  766.         int                    timeout;
  767.  
  768.         for (timeout=10,rate = BR_MAX;rate >= BR_MIN_AUTO_CONF;rate--,timeout*=2) {
  769.             if ([self setPort:thePort atRate:rate] < 0) {
  770.                 fprintf(stderr,"\nsetPort:atRate:%d failed.\n", rate);
  771.             } else {
  772.                    if (ioctl(fd, TIOCCBRK, (void*)nil) < 0) {    /* clear break bit. */
  773.                     fprintf(stderr,
  774.                         "\nioctl(TIOCCBRK) failed on %s:%s\n", portString(thePort),
  775.                         strerror(errno));
  776.                 }
  777.                 xmitAckTimeoutMsecs = timeout;
  778.                 if ([self test:"Attempting to determine baud rate for remote interface."] >= 0)
  779.                     break;
  780.                    if (ioctl(fd, TIOCSBRK, (void*)nil) < 0) {    /* set break bit. */
  781.                     fprintf(stderr,
  782.                         "\nioctl(TIOCSBRK) failed on %s:%s\n", portString(thePort),
  783.                         strerror(errno));
  784.                 }
  785.                 cthread_yield();
  786.             }
  787.         }
  788.         if (rate < BR_MIN_AUTO_CONF) {
  789.             if ([self setPort:thePort atRate:BR_MAX] < 0) {
  790.                 [self free];
  791.                 self = nil;
  792.             }
  793.         }
  794.     } else if ([self setPort:thePort atRate:theRate]) {
  795.         [self free];
  796.         self = nil;
  797.     }
  798.  
  799.     if (self != nil) {
  800.         xmitAckTimeoutMsecs = XMIT_TIMEOUT_MSECS;
  801.         cthread_detach(cthread_fork((cthread_fn_t)theListener, (any_t)self));
  802.     }
  803.     return self;
  804. }
  805.  
  806.  
  807.  
  808. /*    -initPort:
  809.  *    open specified port and search for match baud rate. Returns nil on failure.
  810.  */
  811. - initPort:(sccPortType)thePort 
  812. {
  813.     return [self initPort:thePort atRate:brNone];
  814. }
  815.  
  816.  
  817.  
  818. /*    - init
  819.  *
  820.  *    open default port (A) and search for match baud rate. Returns nil on failure.
  821.  */
  822. - init
  823. {
  824.     return [self initPort:sccPortA atRate:brNone];
  825. }
  826.  
  827.  
  828. -setDelegate:myDelegate
  829. {
  830.     delegate = myDelegate;
  831.     return self;
  832. }
  833.  
  834. -delegate
  835. {
  836.     return delegate;
  837. }
  838.  
  839. @end
  840.  
  841. /*    pktSnooper()
  842.  *
  843.  *    Browses first *size characters in pkt.  If a pkt is found, it is left-justified in the array
  844.  *    and it's length is returned - otherwize zero is returned.
  845.  */
  846. static int pktSnooper(char *head, int *size, id self)
  847. {
  848.     char                    *cp, 
  849.                             *wp;
  850.     int                        retVal, i, garbage;
  851.     
  852.     if (debugFlag > 10)
  853.         fprintf(stderr,"\nSnoopy called with %d for %s\n", *size, stringForCharArray(head, *size));
  854.     if (*size > 0 && *head != SOP_CHAR_CODE) {
  855.         if (debugFlag)
  856.             fprintf(stderr,"\nSnoopy Found packet w/o head\n");
  857. /*        [self sendPkt:NACK_PKT data:NULL len:0 pktId:NACK_ID]; */
  858.         for (garbage=i=0,cp=head;i<*size; i++) {
  859.             if (*cp++ == SOP_CHAR_CODE)
  860.                 break;
  861.             garbage++;
  862.         }
  863.         if (debugFlag) 
  864.             fprintf(stderr,"\nNuking %d Garbage chars\n", garbage);
  865.         *size -= garbage;
  866.         for (i=0,wp=head;i<*size;i++)
  867.             *wp++ = *cp++;
  868.     }
  869.     for (wp=cp=head,garbage=i=0;i<*size;i++) {
  870.         if ((*cp == SOP_CHAR_CODE) && (cp > head)) {
  871.             if (debugFlag) 
  872.                 fprintf(stderr,"\nNuking %d Garbage chars\n", (int)(cp-head));
  873.             garbage+=(cp-head);
  874.             wp = head;
  875.             *wp++ = *cp++;
  876.         } else if (*cp == EOP_CHAR_CODE) {
  877.             *wp++ = *cp++;
  878.             if (*head == SOP_CHAR_CODE) {
  879.                 break;
  880.             }
  881.         } else if (ILLEGAL_CHAR(*cp)) {
  882.              garbage++;
  883.              cp++;
  884.         } else {
  885.             *wp++ = *cp++;
  886.         }
  887.     }
  888.     if (i < (*size))
  889.         retVal =  i+1;
  890.     else
  891.         retVal =  0;
  892.     *size -= garbage;
  893.     if (debugFlag > 10)
  894.         fprintf(stderr,"\nSnoopy returning %d for %s with size:%d\n", retVal, stringForCharArray(head, *size),*size);
  895.     return retVal;
  896. }
  897.  
  898.  
  899. /* theListener()
  900.     An independent background thread that continuously reads the recv port.  Incoming data 
  901.     is read into RCV_IN_BASKET buffer.  Once an entire pkt is in the buffer, processPkt is 
  902.     called to verify the packet's checksum and to build and send an appropriate reply packet.  
  903.     If the packet was a DATA packet, then any escape sequences in the data field are processed 
  904.     to restore any non PASCII characters and the data field is shifted to the front of the 
  905.     buffer and the function returns the length of the data field.  The thread then waits until 
  906.     RCV_OUT_BASKET is empty (The application thread finishes with the buffer and sets 
  907.     dataPktLen= -1)  before swapping IN and OUT baskets.  The last thing the thread does is set 
  908.     dataPktLen to the length of the data buffer to let the application thread know that data is 
  909.     available.
  910.  */
  911. static void theListener(SerialCommDevice *self)
  912. {
  913.     char             *cp, *pkt;
  914.     int                i,
  915.                     len, 
  916.                     oldSize, 
  917.                     newSize;
  918.  
  919.     for (oldSize=newSize=0, pkt=self->recvBuf[RCV_IN_BASKET];;) {    
  920.         for (;;) {
  921.             if ((newSize = read(self->fd, pkt+oldSize, SCD_MAX_PACKETSIZE-oldSize)) < 0)
  922.                 fprintf(stderr,"\nread() failed on %s:%s\n", portString(self->port), strerror(errno));
  923.             oldSize+=newSize;
  924.             if (debugFlag > 10)
  925.                 fprintf(stderr,"\nPkt:%s\n", stringForCharArray(pkt,oldSize));
  926.             if ((len = pktSnooper(pkt, &oldSize, self)) > 0)
  927.                 break;
  928.             cthread_yield();
  929.         }
  930.         do {
  931.             cp = &pkt[len];
  932.             oldSize -= len;
  933.             if ((newSize = [self processPkt:pkt len:len]) >= 0) {
  934.                 while (self->dataPktLen >= 0)     /* wait for primary thread to finish with it's buffer. */
  935.                     cthread_yield();
  936.                 self->dataPktIndex = !self->dataPktIndex;                     /* switch buffers. */
  937.             }
  938.         
  939.             pkt=self->recvBuf[RCV_IN_BASKET];
  940.             for (i=0;i<oldSize;i++)      /* copy remainder of data into head of buffer. */
  941.                 pkt[i] = *cp++;
  942.             if (self->dataPktLen < 0)
  943.                 self->dataPktLen = newSize;
  944.             len = pktSnooper(pkt, &oldSize, self);
  945.         } while (len > 0);
  946.     }
  947. }
  948.  
  949. @implementation SCDDelegate
  950. -oolMessage:(char *)bufr len:(int)l {return self;}
  951. @end
  952.